package com.iamk.segment;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Random;

import javax.imageio.ImageIO;

import com.iamk.util.ConvertColorSpace;

//https://sites.google.com/site/dataclusteringalgorithms/fuzzy-c-means-clustering-algorithm
public class FuzzyCMean {
	int n;// is the number of data points.
	int m;// is the fuzziness index m € [1, ∞]. default 2
	int c;// represents the number of cluster center.
	ArrayList<Pixel> listPixel;
	double epsilon;
	double[][] U; // '�ij' represents the membership of ith data to jth cluster
					// center.
	HashMap<String, ArrayList<Pixel>> train = new HashMap<>();
	ArrayList<Pixel> test = new ArrayList<Pixel>();

	public FuzzyCMean(ArrayList<Pixel> list, int m, int c) {
		// TODO Auto-generated constructor stub
		this.listPixel = list;
		this.m = m;
		this.c = c;
		this.n = list.size();
		U = new double[n][c];

		// Create train
		for (int i = 0; i < c; i++) {
			train.put("" + (i + 1), new ArrayList<Pixel>());
		}
	}

	public void partition() {
		// Randomly select ‘c’ cluster centers
		randomU();
		ArrayList<Vector> centerNew = calculateC();
		ArrayList<Vector> centerOld;
		double exponent = 2.0 / (m - 1);
		// System.out.println(toString());
		for (int m = 0;; m++) {
			centerOld = centerNew;
			// System.out.println(toString());
			// 3. update membership matrix
			for (int i = 0; i < listPixel.size(); i++) { // loop over patterns
				for (int j = 0; j < c; j++) { // loop over clusters
					double denominator = 0;
					double xi_cj_distance = Vector.euclideanDistance(listPixel.get(i).getFeatures(), centerNew.get(j));
					for (int r = 0; r < c; r++) {
						double xi_cr_distance = Vector.euclideanDistance(listPixel.get(i).getFeatures(),centerNew.get(r));
						double ratio = Math.pow(xi_cj_distance / xi_cr_distance, exponent);
						denominator += ratio;
					}
					if (Double.isNaN(denominator)) {
						U[i][j] = 1;
					} else {
						U[i][j] = 1.0d / denominator;
					}
				}
			}
			// update fuzzy centers 'vj'
			centerNew = calculateC();
			if (centerNew.equals(centerOld))
				break;
		}
		// Set data in cluster
		ArrayList<Pixel> tempPixels;
		for (int i = 0; i < n; i++) {
			double temp = 0;
			String label = "";
			for (int j = 0; j < c; j++) {
				if (temp < U[i][j]) {
					temp = U[i][j];
					label = "" + (j + 1);
				}
			}
			// update hash Train
			tempPixels = train.get(label);
			listPixel.get(i).uij = temp;
			tempPixels.add(listPixel.get(i));
			train.put(label, (ArrayList<Pixel>) tempPixels.clone());
		}
		// Set 1/10
		ArrayList<Pixel> temp2 = new ArrayList<Pixel>();
		for (int i = 0; i < c; i++) {
			tempPixels = train.get("" + (i + 1));
			Collections.sort(tempPixels);
			for (int j = 0; j < tempPixels.size(); j++) {
				if (j < tempPixels.size() / 10) {
					temp2.add(tempPixels.get(j));
				} else {
					tempPixels.get(j).uij = 0;
					test.add(tempPixels.get(j));
				}
			}
			train.put("" + (i + 1), (ArrayList<Pixel>) temp2.clone());
			temp2.clear();
		}
	}

	private void randomU() {
		// TODO Auto-generated method stub
		U = new double[n][c];
		for (int i = 0; i < c; i++) {
			U[i][i] = 1;
		}
		// for each pattern, randomly choose a cluster for it
		Random random = new Random(Calendar.getInstance().getTimeInMillis());
		for (int i = c; i < n; i++) {
			int clusterIndex = random.nextInt(c);
			U[i][clusterIndex] = 1;
		}
		// System.out.println( toString());
	}

	protected ArrayList<Vector> calculateC() {
		ArrayList<Vector> C = new ArrayList<Vector>();
		for (int j = 0; j < c; j++) {
			double[] values = new double[listPixel.get(0).getFeatures().length];
			// we need to iteration to get each dimension
			for (int dimIndex = 0; dimIndex < values.length; dimIndex++) {
				double nominator = 0;
				for (int i = 0; i < listPixel.size(); i++) {
					nominator += Math.pow(U[i][j], m)
							* listPixel.get(i).getFeatures()[dimIndex];
				}
				double denominator = 0;
				for (int i = 0; i < listPixel.size(); i++) {
					denominator += Math.pow(U[i][j], m);
				}
				values[dimIndex] = nominator / denominator;
			}
			C.add(new Vector(values));
		}
		return C;
	}

	public double[][] getU() {
		return U;
	}

	public ArrayList<Pixel> getTest() {
		return test;
	}

	public HashMap<String, ArrayList<Pixel>> getTrain() {
		return train;
	}

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		StringBuffer str = new StringBuffer();
		for (int i = 0; i < U.length; i++) {
			for (int j = 0; j < U[0].length; j++) {
				str.append(U[i][j] + " ");
			}
			str.append("\n");
		}
		return str.toString();
	}

	public static void main(String[] args) {
		long start=System.currentTimeMillis();
		File f = new File("lena.bmp");
		try {
			BufferedImage images = ImageIO.read(f);
			int[][] temp = ConvertColorSpace.convertRGBtoY(images);
			ArrayList<Pixel> pixel = new ArrayList<>();
			for (int i = 0; i < images.getHeight(); i++) {
				for (int j = 0; j < images.getWidth(); j++) {
					Pixel p = new Pixel();
					p.features[0]=temp[i][j];
					pixel.add(p);
				}
			}
			FuzzyCMean fuzzyCMean = new FuzzyCMean(pixel, 2, 5);
			fuzzyCMean.partition();
			double[][] result = fuzzyCMean.getU();
			int r = 0;
			for (int i = 0; i < result.length; i++) {
				double t = 0;
				int label = 0;
				for (int j = 0; j < result[0].length; j++) {
					if (result[i][j] > t) {
						t = result[i][j];
						label = j;
					}
//					r++;
				}
				r++;
				System.out.print(label);
				if (r % images.getWidth() == 0) {
					System.out.println();
//					System.out.println("R "+r);
				}
			}
//			System.out.println("W "+images.getWidth());
			System.out.println("Size test : "+fuzzyCMean.getTest().size());
			for(int i=0;i<5;i++){
				ArrayList<Pixel> arrayList=fuzzyCMean.getTrain().get(""+(i+1));
				System.out.println("Train label "+(i+1)+" size: "+arrayList.size());
			}
			long end=System.currentTimeMillis();
			System.out.println("Time "+(end-start)/1000);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

